from Xlib import X, XK                      # X server and X keyboard
from Xlib.display import Display            # X display
from Xlib.display import colormap           # X colourmap
import subprocess                           # Launch external processes
import time

from src.config import Config
from src.tree import Tree

class mwm:

    def __init__(self):
        self.display = Display()
        self.colormap = self.display.screen().default_colormap
        self.root_window = self.display.screen().root
        self.root_window.change_attributes(event_mask = X.SubstructureRedirectMask)  # Redirect events from root window

        self.window_list = list()
        self.current_mode = None
        self.active_window = None
        self.displayWidth = self.root_window.get_geometry().width  # Get width of display
        self.displayHeight = self.root_window.get_geometry().height  # Get height of display
        self.is_work = True

        self.tree = Tree() 

        self.is_tiling = True 

        self.configure_keys()

    """Destroy an active window"""
    def destroy_window(self, event):
        try:
            self.active_window.destroy()
            self.window_list.remove(self.active_window)
            self.active_window = None
        except:
            pass#log(2, "No focused window!")

    def update_focus(self) -> bool:
        window = self.display.screen().root.query_pointer().child
        if window != 0 and window != self.active_window:
            self.active_window = window
            return True
        else: return False

    def update_size(self):
        x, y, width, height = 0, 0, self.displayWidth, self.displayHeight
        nodes = self.tree.get_all_nodes()
        geometries = {}
        is_first = True
        index = 0
        for node in nodes:
            if is_first and index > 2: 
                height = int(height/2)
                y += height
            if not is_first:
                width = int(width/2)
                x += width
            if is_first: is_first = False
            else: is_first = True
            index += 1
            geometries[node] = [x, y, width, height]
        for node, geometry in geometries.items():
            node.value.configure(x=geometry[0], y=geometry[1], width=geometry[2], height=geometry[3])

    def update_borders(self):
        for window in self.window_list:
            #gc = self.root_window.create_gc()
            #window.fill_rectangle(gc, 0, window.get_geometry().y-5, window.get_geometry().width, 5)
            #window.draw_text(gc, window.get_geometry().width/2, 5, "Hello, World!", "ff0011")
            if window != self.active_window: borderColour = "#232323"
            else: borderColour = "#ffffff"
            borderColour = self.colormap.alloc_named_color(borderColour).pixel #borderColour).pixel
            window.configure(border_width = 1)
            window.change_attributes(None,border_pixel=borderColour)
        self.display.sync()

    """Handle WM events"""
    def handle_events(self):
        ignoredEvents = [3, 33, 34, 23]  # Blacklisted events
        if self.display.pending_events() > 0:
            event = self.display.next_event()  # Get next event from display 
        else: return
        if event.type in ignoredEvents: return
        if event.type in [X.FocusIn, X.FocusOut]:
            is_updated = self.update_focus()
            if is_updated: self.update_borders()
        elif event.type == X.MapRequest: self.handle_map(event)  # Send mapping events to the mapping handler
        elif event.type == X.KeyPress: self.handle_key_press(event)  # Send keypress event to the keypress handler
        #elif event.type in ignoredEvents: log(3, "Ignoring event: "+str(event.type))  # Ignore event if it is a blacklisted event
        #else:  # Otherwise, if the event is not a currently handled event
            #log(1, "Unhandled event: "+str(event.type))  # Warn of an unhandled event

    """Handle a mapping request"""
    def handle_map(self, event):
        self.window_list.append(event.window)  # Add the window identifier to a list of open windows
        self.active_window = event.window  # Set the active window to the mapped window
        self.active_window_name = event.window.get_wm_name()  # Set the active window name to the window title
        event.window.map()  # Map the window
        self.active_window.change_attributes(event_mask=X.FocusChangeMask)
        self.update_borders()
        self.tree.add(event.window)
            
        if self.is_tiling:
            self.update_size()

    """Receive a KeyPressed event when a certain key is pressed"""
    def grab_key(self, codes, modifier):
        for code in codes:  # For each code
            self.root_window.grab_key(code, modifier, 1, X.GrabModeAsync, X.GrabModeAsync)  # Receive events when the key is pressed

    """Bind keys"""
    def configure_keys(self):
        for key in Config.grabbed_keys:  # For each key to grab,
            self.grab_key(key, X.Mod4Mask)  # Grab the key with the modifer of Alt

    """Change the position of an active window"""
    def move_window(self, direction):
        if self.active_window != None:
            if direction == "left":
                windowX = self.active_window.get_geometry().x  # Get the current position of the active window
                self.active_window.configure(x=windowX-5)  # Decrease the X position to move it left
            elif direction == "right":
                windowX = self.active_window.get_geometry().x
                self.active_window.configure(x=windowX+5)
            elif direction == "up":
                windowY = self.active_window.get_geometry().y
                self.active_window.configure(y=windowY-5)    
            elif direction == "down":
                windowY = self.active_window.get_geometry().y
                self.active_window.configure(y=windowY+5)
            else:
                pass#log(1, "Invalid movement direction!")  

    """Handle key presses"""
    def handle_key_press(self, event): 
        for key in Config.keys.keys():
            if event.detail in key.value:
                exec(Config.keys[key])

      #  if self.current_mode == "resize":                                                             # If resize mode is enabled:
            #if event.detail in self.left: self.resizeWindow(event.window, 0)                        # Alt+Left Arrow: resize window (x-1)
           # elif event.detail in self.right: self.moveWindow(event.window, 1)                       # Alt+Right Arrow: resize window (x+1)
          #  elif event.detail in self.up: self.moveWindow(event.window, 2)                          # Alt+Up Arrow: resize window (y-1)
         #   elif event.detail in self.down: self.moveWindow(event.window, 3)                        # Alt+Down Arrow: resize window (y+1)
     #   else:                                                                                       # Otherwise,
        #else:
        #    print("key not used")#log(1, "Unhandled key event!")

    """Run an application/process"""
    def run_process(self, command):
        try:
            #log(0, "Running: "+name)
            subprocess.Popen(command)  # Run the command and disown the child process
        except BaseException as error:
            print(error)
            #log(2, "Failed to launch process: "+processCommand+"!")
            #log(3, str(error))

    """Close connection to display server"""
    def close_display(self):
        #log(0, "Exiting...")
        self.display.close()

    def main_loop(self):
        while self.is_work:
            self.handle_events()
            time.sleep(0.016)

    def start(self):
        Config.on_startup()
        self.main_loop()
        self.close_display()



